#!/usr/bin/evn python
# -*- coding: utf-8 -*-
"""
-------------------------------------------------
   文件名称 :     __init__.py
   文件功能描述 :   功能描述
   创建人 :       小钟同学
   创建时间 :          2021/7/27
-------------------------------------------------
   修改描述-2021/7/27:
-------------------------------------------------
 # await core_app.state.redis.set("my-key", "valueaaaaaaaaaaaa")
            # value = await core_app.state.redis.get("my-key")
            # print(value)
            # print("HASH字典的操作")
            # await self.hmset_dict("hash", key1="value1", key2="value2", key3=123)
            # result = await self.hgetall("hash")
            # print("HASH字典的操作",result)

            # result  = await self.add_str_ex('sdsds','sssssssssssssss')
            # print(result)
            # value = await core_app.state.redis.get("sdsds")
            # print(value)
"""

from typing import Tuple, Any

import aioredis
from fastapi import FastAPI
from contextlib import asynccontextmanager
import asyncio
import json
import datetime
from typing import Set, Any, Optional
# from functools import cached_property, lru_cache
# Python 3.8的cached_property
from aioredis import Redis


class AsyncRedisClient():

    # @cached_property
    async def init_aioredis_pool(self, setting) -> Redis:
        self.redis_db = aioredis.from_url(url=setting.from_url, encoding=setting.encoding,
                                          decode_responses=setting.decode_responses)
        return self.redis_db

    def set_redis(self, redis_db: Redis) -> Redis:
        self.redis_db = redis_db
        return self.redis_db

    def get_redis(self) -> Redis:
        return self.redis_db

    async def get_with_ttl(self, key: str) -> Tuple[int, str]:
        async with self.redis_db.pipeline(transaction=True) as pipe:
            return await (pipe.ttl(key).get(key).execute())

    async def get(self, key) -> str:
        return await self.redis_db.get(key)

    async def set(self, key: str, value: str, expire: int = None):
        return await self.redis_db.set(key, value, ex=expire)

    async def setex(self, key, seconds, value):
        return await self.redis_db.setex(key, seconds, value)

    async def pttl(self, key: str) -> int:
        """Get PTTL from a Key"""
        return int(await self.redis_db.pttl(key))

    async def ttl(self, key: str) -> int:
        """Get TTL from a Key"""
        return int(await self.redis_db.ttl(key))

    async def pexpire(self, key: str, pexpire: int) -> bool:

        return bool(await self.redis_db.pexpire(key, pexpire))

    async def expire(self, key: str, expire: int) -> bool:

        return bool(await self.redis_db.expire(key, expire))

    async def incr(self, key: str) -> int:
        """Increases an Int Key"""
        return int(await self.redis_db.incr(key))

    async def decr(self, key: str) -> int:
        """Decreases an Int Key"""
        return int(await self.redis_db.decr(key))

    async def hmset_dict(self, key, **val) -> str:
        return await self.redis_db.hmset_dict(key, **val)

    async def hgetall(self, key, ):
        return await self.redis_db.hgetall(key, encoding="utf-8")

    # 不存在则加入,否则不变
    async def add_str_nx(self, key, values):  # value可以为复杂的json
        return await self.redis_db.setnx(key, values)

    # 加入缓存,存在会替换,并加入过期时间
    async def add_str_ex(self, key, values, time=10):  # value可以为复杂的json
        return await self.redis_db.setex(key, time, values)

    async def clear(self, namespace: str = None, key: str = None) -> int:
        if namespace:
            lua = f"for i, name in ipairs(redis.call('KEYS', '{namespace}:*')) do redis.call('DEL', name); end"
            return await self.redis_db.eval(lua, numkeys=0)
        elif key:
            return await self.redis_db.delete(key)

    async def check_lock(self, key):
        """
        检查当前KEY是否有锁
        """
        key = 'lock:%s' % key
        status = await self.redis_db.get(key)
        if status:
            return True
        else:
            return False

    async def acquire_lock(self, key, expire=30, step=0.03):
        """
        为当前KEY加锁, 默认30秒自动解锁
        """
        key = 'lock:%s' % key
        while 1:
            get_stored = await self.redis_db.get(key)
            if get_stored:
                await asyncio.sleep(step)
            else:
                lock = await self.redis_db.setnx(key, 1)
                if lock:
                    await self.redis_db.expire(key, expire)
                    return True

    async def release_lock(self, key):
        """
        释放当前KEY的锁
        """
        key = 'lock:%s' % key
        await self.safe_delete(key)

    @asynccontextmanager
    async def with_lock(self, key, expire=30, step=0.03):
        """
        @desc redis分布式锁封装
        :param key: 缓存key
        :param expire: 锁失效时间
        :param step: 每次尝试获取锁的间隔
        :return:
        for example:
        with RedisCacheProxy().with_lock("key_name") as lock:
            "do something"
        """
        try:
            t = await self.acquire_lock(key, expire, step)
            yield t
        finally:
            await self.release_lock(key)

    async def get_many(self, keys: list) -> list:
        """
        @desc 批量获取字符串
        :params keys: [chan1, char2]
        """
        data = await self.redis_db.mget(*keys, encoding="utf-8")
        return data

    async def set_many(self, data: dict):
        """批量设置字符串缓存"""
        data = await self.redis_db.mset(data)
        return data

    async def get_data(self, key: str) -> str:
        """获取字符串数据并尝试转换json"""
        value = await self.redis_db.get(key)
        if value:
            try:
                value = json.loads(value.decode("utf-8"))
            except:
                pass
        return value

    async def set_data(self, key: str, value, ex: int = None):
        """尝试转正json字符串存储"""
        try:
            value = json.dumps(value)
        except:
            pass
        return self.redis_db.set(key, value, ex=ex)

    async def delete(self, key):
        """直接删除一个key"""
        await self.redis_db.delete(key)

    async def safe_delete(self, key: str):
        """失效一个key"""
        await self.redis_db.expire(key, -1)

    async def delete_many(self, keys: list) -> None:
        """批量key失效"""
        await self.redis_db.delete(*keys)

    async def exists(self, key: str) -> bool:
        """查询key是否存在"""
        data = await self.redis_db.exists(key)
        return data

    def hget(self, key: str, field: str):
        """获取hash类型一个键值"""
        return self.redis_db.hget(key, field)

    def hmget(self, key: str, fields: list):
        """
        批量获取hash类型键值
        :param key:
        :param fields:
        :return:
        """
        return self.redis_db.hmget(key, fields)

    async def hget_data(self, key: str, field: str) -> Any:
        """获取hash的单个key"""
        data = await self.redis_db.hget(key, field)
        return json.loads(data) if data else None

    async def hmget_data(self, key: str, fields: list) -> list:
        """
        @desc hash类型获取缓存返回一个list
        """
        data = await self.redis_db.hmget(key, *fields)
        return [json.loads(i) if i is not None else None for i in data]

    async def hmget2dict_data(self, key: str, fields: list) -> dict:
        """
        @desc hash类型获取缓存返回一个dict,尝试转换json格式
        """
        cache_list = await self.redis_db.hmget(key, fields)
        return dict(zip(fields, [json.loads(i) if i is not None else None for i in cache_list]))

    async def get_json(self, key: str) -> dict:
        """
        @desc　获取ｊｓｏｎ格式的字典数据
        """
        data = await self.redis_db.hgetall(key)
        if data:
            return {k: json.loads(v) for k, v in dict(data).items()}
        return {}

    async def set_json(self, key: str, value: dict, ex: int = None):
        """
        @desc 使用hash存贮json结构的数据
        :return:
        """
        cache_data = []
        for k, v in value.items():
            cache_data.extend([k, json.dumps(v)])
        if not cache_data:
            return True
        pipe = self.redis_db.pipeline()
        pipe.hmset(key, *cache_data)
        if ex:
            pipe.expire(key, int(ex))
        res = await pipe.execute()
        return res

    async def sadd(self, key: str, values: list) -> int:
        """添加元素"""
        if not values:
            return 0
        count = await self.redis_db.sadd(key, *values)
        return count

    async def spop(self, key: str, count: int = None) -> list:
        """从集合弹出元素"""
        count = 1 if not count else count
        values = await self.redis_db.spop(key, count=count)
        return values if values else []

    async def smembers(self, key: str) -> list:
        """返回一个集合所有元素"""
        values = await self.redis_db.smembers(key)
        return values if values else []

    async def smembers_back_set(self, key: str) -> Set:
        """Gets Set Members"""
        return set(await self.redis_connection.smembers(key))

    async def scard(self, key: str) -> int:
        """获取一个集合的元素个数"""
        count = await self.redis_db.scard(key)
        return count

    async def zadd(self, key, *args, **kwargs):
        # redis zadd操作(批量设置值至args有序集合中)
        if not (args or kwargs):
            return False
        count = await self.redis_db.zadd(key, *args, **kwargs)
        return count

    async def zrem(self, key, member, *members):
        # redis zrem操作(删除name有序集合中的特定元素)
        if not key:
            return False
        count = await self.redis_db.zrem(key, member, *members)
        return count

    async def zincrby(self, key, name, value, amount=1):
        # 如果在key为name的zset中已经存在元素value，则该元素的score增加amount，否则向该集合中添加该元素，其score的值为amount
        if not (name or value):
            return False
        return await self.redis_db.zincrby(key, value, amount)

    async def zrevrank(self, key, value):
        if not value:
            return False
        return await self.redis_db.zrevrank(key, value)

    async def zscore(self, key, member):
        if not member:
            return False
        return self.redis_db.zscore(key, member)

    async def setbit(self, key: str, offset: int, value: int) -> int:
        """
        1:设置或者清空 key 的 value 在 offset 处的 bit 值（只能是 0 或者 1）
        2:只需要一个 key = login_status 表示存储用户登陆状态集合数据， 将用户 ID 作为 offset，在线就设置为 1，下线设置 0。
        3:需要注意的是 offset 从 0 开始
        """
        count = await self.redis_db.setbit(key, offset, value)
        return count

    async def getbit(self, key: str, offset: int) -> int:
        """
        1:获取 key 的 value 在 offset 处的 bit 位的值，当 key 不存在时，返回 0。
        """
        count = await self.redis_db.getbit(key, offset)
        return count

    async def bitcount(self, key: Any) -> int:
        """
        该指令用于统计给定的 bit 数组中，值 = 1 的 bit 位的数量。
        """
        count = await self.redis_db.bitcount(key)
        return count

    async def bitpos(self, key: Any, bit: Any, start=None, end=None) -> int:
        """
        1:返回数据表示 Bitmap 中第一个值为 bitValue 的 offset 位置。
        2:在默认情况下， 命令将检测整个位图， 用户可以通过可选的 start 参数和 end 参数指定要检测的范围。
        """
        count = await self.redis_db.bitpos(key, bit, start=start, end=end)
        return count

    # 签到功能的处理
    async def set_sign_status(self, user_id: int, _singe_key='sign_in:', day=None, statue=1) -> int:
        # 用户签到: 使用日期的来做key
        if not day:
            day = str(datetime.datetime.now())[:10]
        return await self.setbit('{}:{}'.format(_singe_key, day), user_id, statue)

    # 获取用户签到的状-当前日志用户今日签到状态,默认是当前的日期
    async def get_sign_status(self, user_id: int, _singe_key='sign_in:', day=None) -> int:
        if not day:
            day = str(datetime.datetime.now())[:10]
        return await self.getbit('{}:{}'.format(_singe_key, day), user_id)

    # 查询用户求出这个周的签到状况,和总数
    async def get_user_week_sign_status(self, user_id: int, _singe_key='sign_in:') -> tuple:
        now = datetime.datetime.now()
        # 周一是1 周日是7 now.weekday()则是周一是0，周日是6
        weekday = now.isoweekday()
        pipe = self.redis_db.pipeline()
        for d in range(weekday):
            check_day = str(now - datetime.timedelta(days=1) * d)[:10]
            pipe.getbit('{}:{}'.format(_singe_key, check_day), user_id)
        res = await pipe.execute()
        return res[::-1], sum(res[::-1])

    # 查询用户求出这个月的签到状和总数
    async def get_user_month_sign_status(self, user_id: int, _singe_key='sign_in:') -> tuple:
        now = datetime.datetime.now()
        # 周一是1 周日是7 now.weekday()则是周一是0，周日是6
        day = now.day
        pipe = self.redis_db.pipeline()
        for d in range(day):
            check_day = str(now - datetime.timedelta(days=1) * d)[:10]
            pipe.getbit('{}:{}'.format(_singe_key, check_day), user_id)
        res = await pipe.execute()
        return res[::-1], sum(res[::-1])
